# named pipe Client
#encoding: utf-8

import os
import time
import json
import base64

class PipeClientException(Exception):
    def __init__(self, code, value):
        err = '[ Exception {0}] {1}'.format(code, value)
        Exception.__init__(self, err)
        self.code = code
        self.value = value
        
class PipeClient:
    def __init__(self, pipeName, pipeNumber):
        self.pid = os.getppid()
        self.cmd_connect = '@CONT|' + str(self.pid)
        self.cmd_connect_done ='@CONT|OK|'
        self.cmd_close = '@CLOS|' + str(self.pid)
        self.cmd_close_done = '@CLOS|OK|'
        self.cmd_send = '@SEND|'+ str(self.pid)+'|'
        self.cmd_send_done = '@SEND|OK|'
        
        self._pipeName = pipeName
        self._pipeNumber = pipeNumber
        self._connect_idx = -1
        self.rf = None
        
    def pipe_send(self, data, wfp, rfp):
        # Client发送请求
        timeout_count = 0
        rtval = None
        while timeout_count < 3:
            #n = os.open( rfp,  os.O_SYNC | os.O_RDWR )
            #os.write( n, bytes(data, encoding='utf-8') )
            #os.close( n )
            f = os.open( wfp,  os.O_SYNC | os.O_CREAT | os.O_RDWR )
            len_send = os.write( f, bytes(data, encoding='utf-8') )
            os.close( f )
            start = time.time()
            while  time.time()-start<5:
                # Maybe need to close this sleep
                # This sleep is for CPU release !!!!!!!
                time.sleep(0.001)
                #print('timeout_count', timeout_count)
                if self.rf == None:
                    self.rf = os.open( rfp, os.O_RDONLY | os.O_NONBLOCK)
                s = b'A'
                try:
                    s = os.read( self.rf, 4096 )
                    if len(s)<6:
                        #print('return val less then len(6)')
                        continue
                    ''' CRC next time
                    if len(s) != recv_len:
                        timeout_count = timeout_count + 1
                        os.close( rf )
                        continue
                    '''
                    if s[0:5] == b"@SEND" :
                        recv_head = str(s, encoding='utf-8')
                        recv_head = recv_head.replace("@SEND|OK|", "")
                        a = recv_head.split('|',2)
                        data_recv_len = a[0]
                        data_recv = a[1]
                        while int(data_recv_len)>len(data_recv):
                            s = os.read( self.rf, 4096)
                            if len(s)==0:
                                time.sleep(0.001)
                                continue
                            data_recv = data_recv + str(s, encoding='utf-8')
                        rtval = "@SEND|OK|" + data_recv;
                    else:
                        rtval = str(s, encoding='utf-8')

                    
                except OSError as e:
                    pass
                else:
                    break
            
            if rtval == None:
                timeout_count = timeout_count + 1
            else:
                return rtval
            pass
        return rtval
    
    def connect(self):
        timeout_count = 0
        result = False
        while timeout_count<5:
            rtval = self.pipe_send(self.cmd_connect, 
                                    "/tmp/"+self._pipeName+"_"+str(self._pipeNumber)+"_in.pipe", 
                                    "/tmp/"+self._pipeName+"_"+str(self._pipeNumber)+"_out.pipe")
            if rtval==None:
                timeout_count+=1
                continue
            elif len(rtval)<7:
                timeout_count+=1
                continue
            elif rtval==self.cmd_connect_done:
                print(('\033[0;32;40m[ i '+ str(self.pid) +' ' + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + '] \033[0m') + '  CONT|OK : ' + str(self._pipeNumber))
                self._connect_idx = self._pipeNumber
                result = True
                break
            else:
                timeout_count+=1
        if result == False:
            raise(PipeClientException(7, "Can not connect server " + self._pipeName))
            
            
    def close(self):
        if self._connect_idx == -1:
           return
        timeout_count = 0
        result = False
        while timeout_count<5:
            rtval = self.pipe_send(self.cmd_close, 
                                    "/tmp/"+self._pipeName+"_"+str(self._connect_idx)+"_in.pipe", 
                                    "/tmp/"+self._pipeName+"_"+str(self._connect_idx)+"_out.pipe")
            if rtval==None:
                timeout_count+=1
                continue
            elif len(rtval)<7:
                timeout_count+=1
                continue
            elif rtval==self.cmd_close_done:
                print(('\033[0;32;40m[ i '+ str(self.pid) +' ' + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + '] \033[0m')  + '  CLOS|OK : ' + str(self._connect_idx))
                result = True
                self._connect_idx = -1
                break
            else:
                timeout_count+=1
        if result == False:
            raise(PipeClientException(9, "Can not close from server " + self._pipeName))
            
            
    def send(self, buffer, verify_func=None):
        if self._connect_idx == -1:
           raise(PipeClientException(10, "There is no connection for client pipe [" + self._pipeName +"]"))
        
        resend_times = 5
        sendtime_count = 0
        result = False
        #buffer_str = self.cmd_send + str(buffer, encoding='utf-8')
        buffer_str = self.cmd_send + str(len(buffer)) + "|" + buffer
        while True:
            result = False
            rtval = self.pipe_send(buffer_str, 
                                    "/tmp/"+self._pipeName+"_"+str(self._connect_idx)+"_in.pipe", 
                                    "/tmp/"+self._pipeName+"_"+str(self._connect_idx)+"_out.pipe")
            if rtval==None:
                break
                
            elif len(rtval)<7:
                break
                
            elif rtval[0:9]==self.cmd_send_done:
                #print(colored('[ i '+ str(self.pid) +' ' + time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) + '] ', 'green') , 'CLOS|OK : ' + str(self._connect_idx))
                result = True
                return_data={"errcode":0,"rtval":""}

                #Pipe数据格式校验
                try:
                    recv_json_obj=json.loads(rtval[9:])
                except Exception as e:
                    return_data["errcode"]=3000
                    return_data["rtval"]=rtval[9:]
                    print("PipeClient:recv data is not json. data:",rtval[9:]," error:",e)
                    return json.dumps(return_data)
                    
                if verify_func!=None:
                    #server数据状态校验
                    if recv_json_obj["errcode"]==0:
                        try:
                            ret_verify = verify_func(base64.b64decode(recv_json_obj["rtval"]))
                        except Exception as e:
                            return_data["errcode"]=3001
                            return_data["rtval"]=recv_json_obj["rtval"]
                            print("PipeClient:verify_func error. data:",recv_json_obj["rtval"]," error:",e)
                            return json.dumps(return_data)

                        #server数据正确性校验(仅正确返回，尽量规避校验函数写错)
                        if ret_verify==True:
                            return rtval[9:]
                        else:
                            return_data["errcode"]=3002
                            return_data["rtval"]=recv_json_obj["rtval"]
                            print("PipeClient:ret_verify failed. data:",recv_json_obj["rtval"])
                            #with open('/home/pi/hf_formation/pipe_client.log', 'a+') as f:
                            #    f.write("PipeClient:ret_verify failed [3002]. send:"+str(buffer)+"recvdata:"+str(recv_json_obj["rtval"])+"\n")
                            sendtime_count+=1
                            if sendtime_count>=resend_times:
                                return json.dumps(return_data)
                            continue
                    else:
                        #底层数据错误直接返回
                        return rtval[9:]
                else:
                    return rtval[9:]
            else:
                break

        if result == False:
            #return None
            raise(PipeClientException(11, "Send data exception "))
            
